注意: 本章节会省略一些参数或方法的详细说明,因为在Form组件章节中提及到

ModelForm: 

  • 通过 Model类(即: models.py 中的表类) 生成 form类(即: Form组件中所要创建的类)

  • Form 组件有的,ModelForm 组件也有,Form 组件没有的,ModelForm 组件也有

  • 不要认为用了ModelForm组件就一定要用它的自动渲染form表单功能,ModelForm组件只用于验证,不做form表单渲染也是可以的,因为最后都是从 request.POST 中获取数据进行验证,前提是 form表单中的 name 需要和 form类中的字段名一致

ModelForm 的校验:

  • ModelForm 的校验是根据 Model类(即: models.py 中的表类)中的 字段 或 字段参数来判断传递过来的数据是否正确(和Form组件中的校验情况差不多,只不过Form组件是用Form类中的字段进行校验,而ModelForm是用Model类中的字段进行校验)

  • 在 Model类(即: models.py 中的表类)中,EmailField、 URLField、 UUIDField 等字段只有在使用ModelForm的时候才有意义,因为这些字段是在使用ModelForm的时候验证传递过来的数据是否正确的时候使用的

1. 本章节所用到的表

# models.py

from django.db import models


class Book(models.Model):
    title = models.CharField(max_length=15)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish')
    author = models.ManyToManyField(to='Author')

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "书籍"
        verbose_name_plural = verbose_name


class Publish(models.Model):
    title = models.CharField(max_length=15)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = '出版社'
        verbose_name_plural = verbose_name


class Author(models.Model):
    name = models.CharField(max_length=15)
    age = models.IntegerField()
    authorDetail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE, verbose_name='作者详情')  # 与AuthorDetail建立一对一的关系

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '作者'
        verbose_name_plural = verbose_name


class AuthorDetail(models.Model):
    birthday = models.DateField(verbose_name='生日')
    telephone = models.BigIntegerField(verbose_name='电话')
    addr = models.CharField(max_length=64, verbose_name='地址')

    def __str__(self):
        return str(self.telephone)

    class Meta:
        verbose_name = "作者详情表"
        verbose_name_plural = verbose_name

2. 创建 ModelForm

  • 通过Model类,生成form类

# views.py 或 forms.py

from django.shortcuts import render, HttpResponse, redirect
from .models import *
from django.forms import ModelForm
from django.forms import widgets as wid  # 因为widgets和ModelForm中的参数重名了,所以起个别名


class BookForm(ModelForm):
    class Meta:
        model = Book  # 根据 Book 表类创建 Form 类
        fields = '__all__'  # 使用 Book 表类的所有字段,
# fields = ['title', 'price', 'publish', 'author']  # 使用 Book 表类的某几个字段
# exclude = ['publish', 'author']  # 排除某些字段,一般和 fields = '__all__' 搭配使用
        labels = {  # 和 Form 组件中的 label 作用是一样的
            'title': '书籍名称',
            'price': '价格',
            'publish': '出版社',
            'author': '作者'
        }
        error_messages = {  # 错误信息
            'title': {'required': "书籍名称不能为空"},
            'price': {'required': "价格不能为空"},
            'publish': {'required': "请选择出版社"},
            'author': {'required': "请选择作者"},
        }
        widgets = {  # 和 Form 组件中的 widgets作用是一样的
            'title': wid.TextInput(attrs={'class': 'form-control'}),
            'price': wid.TextInput(attrs={'class': 'form-control'}),
            'publish': wid.Select(attrs={'class': 'form-control'}),
            'author': wid.CheckboxSelectMultiple(attrs={'class': 'form-control'})
        }

3. 使用 ModelForm 渲染 Form 表单

  • 和 Form 组件的使用方式是一样的

# views.py

from django.shortcuts import render, HttpResponse, redirect
from .models import *
from django.forms import ModelForm
from django.forms import widgets as wid


class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = '__all__'  
        labels = {
            'title': '书籍名称',
            'price': '价格',
            'publish': '出版社',
            'author': '作者'
        }
        error_messages = {  
            'title': {'required': "书籍名称不能为空"},
            'price': {'required': "价格不能为空"},
            'publish': {'required': "请选择出版社"},
            'author': {'required': "请选择作者"},
        }
        widgets = {  
            'title': wid.TextInput(attrs={'class': 'form-control'}),
            'price': wid.TextInput(attrs={'class': 'form-control'}),
            'publish': wid.Select(attrs={'class': 'form-control'}),
            'author': wid.CheckboxSelectMultiple(attrs={'class': 'form-control'})
        }


def add_book(request):
book_form = BookForm()  # 实例化一个表单类
    return render(request, 'add.html', {'book_form': book_form})  # 将表单类传递给HTML模板,然后渲染出form表单

# add.html

<form action="" method="post" novalidate>
    {% csrf_token %}

    {% for field in book_form %}
        <div>
            {{ field.label }}
            {{ field }}
        </div>
    {% endfor %}

    <input type="submit">
</form>

4.使用 ModelForm 渲染 Form 表单,并且将对应数据自动填写到表单中(即: 修改页面)

  • 通过Form组件渲染form表单,无法将对应数据自动填写到表单中(即:无法实现修改页面的自动填充数据效果),但是 ModelForm 可以实现

  • instance 参数接收一个对象(即: 查询数据后得到的对象)

# views.py

from django.shortcuts import render, HttpResponse, redirect
from .models import *
from django.forms import ModelForm
from django.forms import widgets as wid 


class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = '__all__'  
        labels = {
            'title': '书籍名称',
            'price': '价格',
            'publish': '出版社',
            'author': '作者'
        }
        error_messages = {  
            'title': {'required': "书籍名称不能为空"},
            'price': {'required': "价格不能为空"},
            'publish': {'required': "请选择出版社"},
            'author': {'required': "请选择作者"},
        }
        widgets = {  
            'title': wid.TextInput(attrs={'class': 'form-control'}),
            'price': wid.TextInput(attrs={'class': 'form-control'}),
            'publish': wid.Select(attrs={'class': 'form-control'}),
            'author': wid.CheckboxSelectMultiple(attrs={'class': 'form-control'})
        }


def edit_book(request, edit_book_id):
edit_book_obj = Book.objects.filter(pk=edit_book_id).first()  # 查询指定的book数据
    book_form = BookForm(instance=edit_book_obj# 将查询到的数据传递给 instance 参数,从而实现将对应数据自动填写到表单中(即: 修改页面)
    return render(request, 'add.html', {'book_form': book_form})

# edit.html

<form action="" method="post" novalidate>
    {% csrf_token %}

    {% for field in book_form %}
        <div>
            {{ field.label }}
            {{ field }}
        </div>
    {% endfor %}

    <input type="submit">
</form>

5. 自定义字段所输出的表单 HTML

# datetime_picker.html

<div class="input-group date form_date">
    <input readonly class="form-control"
           type="{{ widget.type }}"
           name="{{ widget.name }}"
            {% if widget.value != None %} value="{{ widget.value|stringformat:"s" }}" {% endif %}
            {% include "django/forms/widgets/attrs.html" %} />
    <span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>
</div>

# widgets.py

from django import forms


class DateTimePickerInput(forms.TextInput):
    template_name = 'forms/widgets/datetime_picker.html'

# forms/coupon.py

from django.forms import ModelForm
from jyld_backstage.models import *
from django.forms import widgets as wid
from xadmin.forms.widgets import DateTimePickerInput


class CouponModelForm(ModelForm):
    class Meta:
        model = Coupon
        fields = '__all__'
        widgets = {
            'title': wid.TextInput(attrs={'class': 'form-control'}),
            'open_date': DateTimePickerInput,
        }


6. 获取 ModelForm 字段的相关信息

# views.py

from django.shortcuts import render
from app01.models import *
from django.forms import ModelForm


class UserInfoForm(ModelForm):
    class Meta:
        model = UserInfo
        fields = '__all__'
        labels = {
            'username': '用户名',
            'phone': '电话号码',
            'age': '年龄',
        }


def edit_data(request):
    user = UserInfo.objects.all().first()
    form = UserInfoForm(instance=user)

# 获取当前标签的相关信息
    # form['username'] 等同于 html 模板中循环出来的 field
    print(form['username'].__dict__)  # {'label': '用户名', 'name': 'username', ……}

# 获取当前标签属性的内容(如: class, id, value 等)
    for data in form['username']:
 # data 等同于 html 模板中循环出来的 field
        print(data.__dict__)  # {'data': {'required': True, 'type': 'text', 'name': 'username', 'template_name': 'django/forms/widgets/text.html', 'is_hidden': False, 'value': 'Amy', 'attrs': {'maxlength': '32', 'id': 'id_username', 'required': True'+'}'+'}'}}, ……}

    return render(request, 'change.html', {
        'form': form
    })

# xxx.html

<form method="post" novalidate>
    {% csrf_token %}
    {% for field in form %}
        <p>
            {{ field.label }}:
            <input
                    type="{{'{'+'{'+' field.0.data.type '+'}'+'}'}}"
                    value="{{'{'+'{'+' field.0.data.value '+'}'+'}'}}"
                    id="{{'{'+'{'+' field.0.data.attrs.id '+'}'+'}'}}"
                    maxlength="{{'{'+'{'+' field.0.data.attrs.maxlength '+'}'+'}'}}"
                    name="{{ field.name }}"
            >
        </p>
    {% endfor %}
    <input type="submit" value="提交">
</form>

7. 添加数据

  • 直接通过 ModelForm 中的 .save() 方法将提交过来的数据添加到数据库中,如果是使用 form 组件就需要自己写 orm 语句将提交过来的数据添加数据库中

  • .save() 方法的返回值就是所添加的该条数据对象

# views.py

from django.shortcuts import render, HttpResponse, redirect
from .models import *
from django.forms import ModelForm
from django.forms import widgets as wid


class BookForm(ModelForm):
    class Meta:
        model = Book  
        fields = '__all__'  
        labels = {  
            'title': '书籍名称',
            'price': '价格',
            'publish': '出版社',
            'author': '作者'
        }
        error_messages = {  
            'title': {'required': "书籍名称不能为空"},
            'price': {'required': "价格不能为空"},
            'publish': {'required': "请选择出版社"},
            'author': {'required': "请选择作者"},
        }
        widgets = {
            'title': wid.TextInput(attrs={'class': 'form-control'}),
            'price': wid.TextInput(attrs={'class': 'form-control'}),
            'publish': wid.Select(attrs={'class': 'form-control'}),
            'author': wid.CheckboxSelectMultiple(attrs={'class': 'form-control'})
        }


def add_book(request):
    if request.method == 'POST':
        book_form = BookForm(request.POST)
        if book_form.is_valid():

            book_data = book_form.save() # 将提交过来的数据保存到数据库
print(book_data.pk, book_data.title)  # 10 三国演义

            return redirect('/books/')
        else:
            print(book_form.errors)
            return HttpResponse('添加失败')
    book_form = BookForm()
    return render(request, 'add.html', {'book_form': book_form})

# add.html

<form action="" method="post" novalidate>
    {% csrf_token %}

    {% for field in book_form %}
        <div>
            {{ field.label }}
            {{ field }}
        </div>
    {% endfor %}

    <input type="submit">
</form>

8. 修改数据

  • 直接通过 ModelForm 中的 instance 参数修改数据库中的数据,如果是使用 form 组件就需要自己写 orm 语句修改数据库中的数据

  • instance 参数接收一个对象(即: 查询数据后得到的对象)

# views.py

from django.shortcuts import render, HttpResponse, redirect
from .models import *
from django.forms import ModelForm
from django.forms import widgets as wid


class BookForm(ModelForm):
    class Meta:
        model = Book  
        fields = '__all__'  
        labels = {  
            'title': '书籍名称',
            'price': '价格',
            'publish': '出版社',
            'author': '作者'
        }
        error_messages = {  
            'title': {'required': "书籍名称不能为空"},
            'price': {'required': "价格不能为空"},
            'publish': {'required': "请选择出版社"},
            'author': {'required': "请选择作者"},
        }
        widgets = {
            'title': wid.TextInput(attrs={'class': 'form-control'}),
            'price': wid.TextInput(attrs={'class': 'form-control'}),
            'publish': wid.Select(attrs={'class': 'form-control'}),
            'author': wid.CheckboxSelectMultiple(attrs={'class': 'form-control'})
        }


def edit_book(request, edit_book_id):
edit_book_obj = Book.objects.filter(pk=edit_book_id).first()
    if request.method == 'POST':
        book_form = BookForm(request.POST, instance=edit_book_obj)  # 验证提交过来的数据是否有误,并且通过 instance 参数指定要修改那条数据
                                                                    # 如果没有指定 instance 参数那么在执行 .save() 方法的时候该操作就是新增数据的操作
                                                                    # 不要和下面的 instance 参数搞混了,虽然 instance 参数的作用是一样的,但是表达的意思是不一样的
                                                                    #(即:下面instance参数表达的是将对应数据自动填写到表单中,而这里表达的是指定要修改那条数据)
        if book_form.is_valid():
            book_data = book_form.save()
            print(book_data.pk, book_data.title)  # 10 三国演义2 
            return redirect('/books/')
        else:
            return HttpResponse('修改失败')
    book_form = BookForm(instance=edit_book_obj)  # 将查询到的数据传递给 instance 参数,从而实现将对应数据自动填写到表单中(即: 修改页面) 
    return render(request, 'add.html', {'book_form': book_form})

# edit.html

<form action="" method="post" novalidate>
    {% csrf_token %}

    {% for field in book_form %}
        <div>
            {{ field.label }}
            {{ field }}
        </div>
    {% endfor %}

    <input type="submit">
</form>

9. 自定义字段

  • 注意: 

    • 定义自定义字段的方法和 form 组件定义字段的方法是一样的

    • 自定义字段的字段名不能和 ModelForm 中所指向的 表类(即: Model类)中的字段名重复

      • 因为如果自定义字段的字段名和当前 ModelForm 所指向的 表类(即: Model类)中的字段名一样,那么该自定义字段就是 表类(即: Model类)的字段而不是自定义字段

  • 使用方式一 -> 确认密码

# forms/password.py

from django.forms import ModelForm
from django.forms import widgets as wid
from django.forms import fields as Ffields
from django.core.exceptions import ValidationError
from .models import *


class RegForm(ModelForm):
confirm_password = Ffields.CharField(
        min_length=6,
        label='确认密码',
        widget=wid.PasswordInput(
            attrs={
                'class': 'form-control',
                'placeholder': '确认密码',
            }
        ),
        error_messages={
            'required': '确认密码不能为空',
            'min_length': '确认密码不能小于6位',
        }
    )

    class Meta:
        model = UserInFo
        fields = ['password', 'confirm_password']
        labels = {
            'password': '密码',
        }
        error_messages = {
            'password': {'required': '密码不能为空', 'min_length': '密码不能小于6位', },
        }
        widgets = {
            'password': wid.PasswordInput(attrs={'class': 'form-control', 'placeholder': '密码'}),
        }

# 重写全局钩子,判断两次密码是否一致
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if password == confirm_password:
            return self.cleaned_data
        else:
            self.add_error('confirm_password', ValidationError('两次密码不一致'))

  • 使用方式二 -> 添加当前表的一对一字段所关联表的内容到当前 form 表单中

    • 自定义字段一般用于当前表类拥有一对一字段,且一对多或多对多很少会用到

    • ModelForm 源码中两个重要属性:

      • self.instance -> 字段对象
      • self.initial -> 初始值(即: 修改页面所显示的每个字段所对应的数据)

# forms/author.py

from django import forms
from django.forms import ModelForm
from app01.models import *


class BootStrapModelForm(ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)  # 调用父类的 __init__ 方法

# 批量添加样式
        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })


class AuthorForm(BootStrapModelForm):
# 自定义字段
birthday = forms.DateField(label='出生日期')
telephone = forms.IntegerField(label='手机号码')
addr = forms.CharField(label='地址')

    class Meta:
        model = Author
        fields = ['name', 'age']
        labels = {
            'name': '作者名称',
            'age': '年龄',
        }
        error_messages = {
            'name': {'required': "作者名称不能为空"},
            'age': {'required': "年龄不能为空"},
            'birthday': {'required': "出生日期不能为空"},
            'telephone': {'required': "手机号码不能为空"},
            'addr': {'required': "地址不能为空"},
        }

# 重写 ModelForm 的 __init__ 方法
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)  # 调用父类的 __init__ 方法

# self.instance -> 字段对象
        # self.initial -> 初始值(即: 修改页面所显示的每个字段所对应的数据)

# 修改数据时所显示的自定义字段的对应数据
        if self.instance.authorDetail_id:  # 使用字段对象下的 一对一 或 多对多 字段属性进行判断
            self.initial['birthday'] = self.instance.authorDetail.birthday
            self.initial['telephone'] = self.instance.authorDetail.telephone
            self.initial['addr'] = self.instance.authorDetail.addr

# 重写 ModelForm 的 save 方法
    def save(self, commit=True):

 # self.instance -> 字段对象
        # self.initial -> 初始值(即: 修改页面所显示的每个字段所对应的数据)

# 注意: 必须将修改数据的判断放到添加数据的判断前面

# 修改数据时自定义字段数据的保存
        if self.instance.authorDetail_id:  # 使用字段对象下的 一对一 或 多对多 字段属性进行判断
            authordetail_queryset = AuthorDetail.objects.filter(pk=self.instance.authorDetail_id).all()
            authordetail_queryset.update(
                birthday=self.cleaned_data['birthday'],
                telephone=self.cleaned_data['telephone'],
                addr=self.cleaned_data['addr'],
            )

 # 该判断中所执行的内容,只适用于当前 Model 表中有 一对一、一对多、多对多字段
        # 新建数据时自定义字段数据的保存
        if not self.instance.authorDetail_id:  # 使用字段对象下的 一对一 或 多对多 字段属性进行判断
# 新建数据时先新建 外键字段 或 一对一字段 的数据
            author_detail_obj = AuthorDetail.objects.create(
                birthday=self.cleaned_data['birthday'],
                telephone=self.cleaned_data['telephone'],
                addr=self.cleaned_data['addr']
            )
# 将新建好的数据 id 赋值给当前字段对象的指定 外键字段 或 一对一字段 上
            self.instance.authorDetail_id = author_detail_obj.pk

        super().save(commit=True)  # 调用父类 ModelForm 的 save 保存方法

        return self.instance  # 一定要返回 self.instance

# views.py

from django.shortcuts import render, HttpResponse, redirect
from .models import *
from app01.forms.author import AuthorForm


def add_author(request):
    author_form = AuthorForm()
    if request.method == 'POST':
        author_form = AuthorForm(request.POST)
        if author_form.is_valid():
            author_form.save()
            return redirect('/authors/')
    return render(request, 'change.html', {'form': author_form})


def edit_author(request, edit_author_id):
    edit_author_obj = Author.objects.filter(pk=edit_author_id).first()
    author_form = AuthorForm(instance=edit_author_obj)
    if request.method == 'POST':
        author_form = AuthorForm(request.POST, instance=edit_author_obj)
        if author_form.is_valid():
            author_form.save()
            return redirect('/authors/')
    return render(request, 'change.html', {'form': author_form})

# change.html

<form action="" method="post" novalidate>
    {% csrf_token %}

    {% for field in form %}
        <div class="form-group {% if field.errors %} has-error {% endif %}">
            {{ field.label }}
            {{ field }}
            <span class="help-block">{{ field.errors.0 }}</span>
        </div>
    {% endfor %}

    <input type="submit" class="btn btn-success">
</form>



10. 保存或修改上传文件

# forms/picture.py

from django.forms import ModelForm
from app01.models import *
from django.forms import widgets as wid


class PictureForm(ModelForm):
    class Meta:
        model = Picture
        fields = '__all__'
        labels = {
            'title': '标题',
            'img': '图片',
        }
        error_messages = {
            'title': {'required': "标题不能为空"},
            'img': {'required': "图片不能为空"},
        }
        widgets = {
            'title': wid.TextInput(attrs={'class': 'form-control'}),
            'img': wid.FileInput(attrs={'class': 'form-control'}),
        }

# views.py

from app01.models import *
from django.shortcuts import render, HttpResponse
from app01.forms.picture import PictureForm


def add_data(request):
    picture_form = PictureForm()
    if request.POST:
        picture_form = PictureForm(request.POST, request.FILES)
        if picture_form.is_valid():
            picture_form.save()
            return HttpResponse('添加成功')
    return render(request, 'change.html', {
        'form': picture_form,
    })


def edit_data(request, edit_pk):
    picture_obj = Picture.objects.filter(pk=edit_pk).first()
    picture_form = PictureForm(instance=picture_obj)
    if request.POST:
        picture_form = PictureForm(request.POST, request.FILES, instance=picture_obj)
        if picture_form.is_valid():
            picture_form.save()
            return HttpResponse('修改成功')
    return render(request, 'change.html', {
        'form': picture_form,
    })

# change.html

<form method="post" enctype="multipart/form-data" novalidate>
    {% csrf_token %}
    {% for field in form %}
        <div>
            {{ field.label }}
            {{ field }}
        </div>
        <p>{{ field.errors.0 }}</p>
    {% endfor %}
    <input type="submit" value="提交">
</form>

11. .queryset.model ->  获取该字段对象下所关联的模型表(即: 表类)

  • 获取form的字段对象方法在Form组件章节中有提到

  • .queryset 只作用于 一对一、一对多、多对多字段

  • 语法: 字段对象.queryset.model

# views.py 或 forms.py

from django.forms import ModelForm

class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = '__all__'


book_form_obj = BookForm()

# 获取 一对多 的字段对象
field_obj = book_form_obj.fields['publish']  # <django.forms.models.ModelChoiceField object at 0x000001F2AC79E7B8>
# 获取该字段对象下所关联的模型表
model = field_obj.queryset.model  # <class 'app01.models.Publish'>

# 获取该模型表下的数据
model_data = model.objects.all()  # <QuerySet [<Publish: 东莞出版社>, <Publish: 广州出版社>]>
# 获取该模型表所在的app名称
app_label = model._meta.app_label  # app01
# 获取该模型表的表名
model_name = model._meta.model_name  # publish

12. 只用 ModelFo组件 进行验证,不做渲染

  • 不要认为用了ModelForm组件就一定要用它的自动渲染form表单功能,ModelForm组件只用于验证,不做form表单渲染也是可以的,因为最后都是从 request.POST 中获取数据进行验证,前提是 form表单中的 name 需要和 form类中的字段名一致

  • 只用ModelForm组件进行验证,不做渲染(即: 自己编写form表单) -> 使用 form 表单提交数据

# 这里就不写例子了,因为和form组件章节中的列子差不多

13. 图书管理例子

model_form_demo.rar

# models.py

from django.db import models


class Book(models.Model):
    title = models.CharField(max_length=15)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish')
    author = models.ManyToManyField(to='Author')

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "书籍"
        verbose_name_plural = verbose_name


class Publish(models.Model):
    title = models.CharField(max_length=15)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = '出版社'
        verbose_name_plural = verbose_name


class Author(models.Model):
    name = models.CharField(max_length=15)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '作者'
        verbose_name_plural = verbose_name

# views.py

from django.shortcuts import render, HttpResponse, redirect
from .models import *
from django.forms import ModelForm
from django.forms import widgets as wid  # 因为widgets和ModelForm中的参数重名了,所以起个别名


class BookForm(ModelForm):
    class Meta:
        model = Book  # 根据 Book 表类创建 Form 类
        fields = '__all__'  # 使用 Book 表类的所有字段,
        # fields = ['title', 'price', 'publish', 'author']  # 使用 Book 表类的某几个字段
        # exclude = ['publish', 'author']  # 排除某些字段,一般和 fields = '__all__' 搭配使用
        labels = {  # 和 Form 组件中的 label 作用是一样的
            'title': '书籍名称',
            'price': '价格',
            'publish': '出版社',
            'author': '作者'
        }
        error_messages = {  # 错误信息
            'title': {'required': "书籍名称不能为空"},
            'price': {'required': "价格不能为空"},
            'publish': {'required': "请选择出版社"},
            'author': {'required': "请选择作者"},
        }
        widgets = {  # 和 Form 组件中的 widgets作用是一样的
            'title': wid.TextInput(attrs={'class': 'form-control'}),
            'price': wid.TextInput(attrs={'class': 'form-control'}),
            'publish': wid.Select(attrs={'class': 'form-control'}),
            'author': wid.SelectMultiple(attrs={'class': 'form-control'})
        }


def books(request):
    book_list = Book.objects.all()
    return render(request, 'books.html', {'book_list': book_list})


def add_book(request):
    book_form = BookForm()
    if request.method == 'POST':
        book_form = BookForm(request.POST)
        if book_form.is_valid():
            book_form.save()  # 将提交过来的数据保存到数据库
            return redirect('/books/')
    return render(request, 'add.html', {'book_form': book_form})


def edit_book(request, edit_book_id):
    edit_book_obj = Book.objects.filter(pk=edit_book_id).first()
    book_form = BookForm(instance=edit_book_obj)
    if request.method == 'POST':
        book_form = BookForm(request.POST, instance=edit_book_obj)  # 验证提交过来的数据是否有误,并且通过 instance 参数指定要修改那条数据
                                                                    # 如果没有指定 instance 参数那么在执行 .save() 方法的时候该操作就是新增数据的操作
                                                                    # 不要和上面的 instance 参数搞混了,虽然 instance 参数的作用是一样的,但是表达的意思是不一样的
                                                                    #(即:上面instance参数表达的是将对应数据自动填写到表单中,而这里表达的是指定要修改那条数据)
        if book_form.is_valid():
            book_form.save()
            return redirect('/books/')

    return render(request, 'edit.html', {'book_form': book_form})

# urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views


urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^books', views.books),
    url(r'^add_book', views.add_book),
    url(r'^edit_book/(\d+)', views.edit_book),
 # url(r'^delete_book', views.delete_book),
]

# books.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Title</title>
    <meta name="description" content="">
    <meta name="keywords" content="">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <a href="/add_book/" class="btn btn-info">添加书籍</a>
    <table border="1" class="table table-bordered">
        <thead>
        <tr>
            <th>id</th>
            <th>书名</th>
            <th>价格</th>
            <th>出版社</th>
            <th>作者</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody>
        {% for book in book_list %}
            <tr>
                <td>{{ book.pk }}</td>
                <td>{{ book.title }}</td>
                <td>{{ book.price }}</td>
                <td>{{ book.publish.title }}</td>
                <td>
                    {% for author in book.author.all %}
                        {{ author.name }}
                    {% endfor %}
                </td>
                <td>
                    <a href="/edit_book/{{ book.pk }}">编辑</a>
{% comment %}<a href="/delete_book/{{ book.pk }}">删除</a>{% endcomment %}
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
</div>

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</body>
</html>

# form.html

<form action="" method="post" novalidate>
    {% csrf_token %}

    {% for field in book_form %}
        <div class="form-group {% if field.errors %} has-error {% endif %}">
            {{ field.label }}
            {{ field }}
            <span class="help-block">{{ field.errors.0 }}</span>
        </div>
    {% endfor %}

    <input type="submit" class="btn btn-success">
</form>

# add.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Title</title>
    <meta name="description" content="">
    <meta name="keywords" content="">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<div class="container">
    <h3>添加页面</h3>
    {% include 'form.html' %}
</div>

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</body>
</html>

# edit.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Title</title>
    <meta name="description" content="">
    <meta name="keywords" content="">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<div class="container">
    <h3>修改页面</h3>
    {% include 'form.html' %}
</div>

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</body>
</html>